////////////////////////////////////////////////////////////////////////////
//
//  Crytek Engine Source File.
//  Copyright (C), Crytek Studios, 2002.
// -------------------------------------------------------------------------
//  File name:   terrain_sector_render.cpp
//  Version:     v1.00
//  Created:     28/5/2001 by Vladimir Kajalin
//  Compilers:   Visual Studio.NET
//  Description: Create buffer, copy it into var memory, draw
// -------------------------------------------------------------------------
//  History:
//
////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"

#include "terrain.h"
#include "terrain_sector.h"
#include "ObjMan.h"
#include "terrain_water.h"
#include "CryThread.h"
#include <IJobManager_JobDelegator.h>

DECLARE_JOB("Terrain_BuildIndices", TBuildIndicesJob, CTerrainNode::BuildIndices_Wrapper );
DECLARE_JOB("Terrain_BuildVertices", TBuildVerticesJob, CTerrainNode::BuildVertices_Wrapper );

// specialized container, which can manage main memory and a SPU local "ghost" memory
// to be used on SPU with an synchron writeback
template<typename Type>
class CTerrainTempDataStorage
{
public:
	CTerrainTempDataStorage();
	
	void Clear();
	void Free();

	void PreAllocate( Type* pMemory, size_t nSize );

	int Count() const {	return m_nCount; }
	Type* GetElements() const { return m_pMemory; }

	void Add( const Type &obj);

	// SPU only interface
	int MemorySize() const { return m_nCap * sizeof(Type); }

	void GetMemoryUsage(ICrySizer *pSizer) const
	{
		pSizer->AddObject(GetElements(),MemorySize());
	}





private:
	SPU_DOMAIN_MAIN Type*		m_pMemory;		// continuous memory block to store objects in main memory
	int			m_nCount;			// number of objects in the container
	int			m_nCap;				// maximum number of objects fitting into the allocated memory

	// these are only used for SPU
	SPU_DOMAIN_LOCAL Type* m_pSPUMemory;		// "ghost" memory allocated on spu to operate there on it
	int m_nOffset;				// byte offset of already to main memory transfered data
	int m_nDmaId;					// DMA Id to use

	static const int m_nChunkSize = 1024; // start transfer each 2kb
};

template<typename Type>
CTerrainTempDataStorage<Type>::CTerrainTempDataStorage() :
	m_pMemory(NULL),
	m_nCount(0),
	m_nCap(0),
	m_pSPUMemory(NULL),
	m_nOffset(0),
	m_nDmaId(0)
{}

template<typename Type>
void CTerrainTempDataStorage<Type>::Clear()
{
	if( m_nOffset != 0 ){ snPause();}

	m_nCount = 0;
}

template<typename Type>
void CTerrainTempDataStorage<Type>::Free()
{
	if( m_nOffset != 0 ) { snPause();}

	m_pMemory = NULL;
	m_nCount = 0;
	m_nCap = 0;
}

template<typename Type>
void CTerrainTempDataStorage<Type>::PreAllocate( Type* pMemory, size_t nSize )
{




	m_pMemory = pMemory;
	m_nCap = nSize;
}

template<typename Type>
void CTerrainTempDataStorage<Type>::Add( const Type &obj)
{
	assert(m_nCount < m_nCap);

	// special case for when a reallocation is needed
	IF( m_nCount == m_nCap, false )
	{
		CryFatalError("terrain sector scheduled update error");
		return;
	}

	// add object
	Type* pMemory = SPU_PTR_SELECT(m_pMemory, m_pSPUMemory);
	pMemory[m_nCount] = obj;
	++m_nCount;













}

// SPU only interface
































struct CStripInfo 
{
  int begin;
  int end;
};

struct CStripsInfo 
{ 
	CTerrainTempDataStorage<unsigned short> idx_array; 
	int nNonBorderIndicesCount;

	inline void Clear() { idx_array.Clear(); nNonBorderIndicesCount=0; } 

	inline void AddIndex(int _x, int _y, int _step,  int nSectorSize)
	{
		unsigned short id = _x/_step*(nSectorSize/_step+1) + _y/_step;
		int nCount = idx_array.Count();
		idx_array.Add(id);
	}

	static uint16 GetIndex(int _x, int _y, int _step,  int nSectorSize)
	{
		unsigned short id = _x/_step*(nSectorSize/_step+1) + _y/_step;
		return id;
	}

	void GetMemoryUsage(ICrySizer *pSizer) const
	{
		pSizer->AddObject(idx_array);
	}
};

class CUpdateTerrainTempData
{	
public:

	JobManager::SJobState m_JobStateBuildIndices;
	JobManager::SJobState m_JobStateBuildVertices;

	void GetMemoryUsage(ICrySizer *pSizer) const
	{
		pSizer->AddObject(m_StripsInfo);
		pSizer->AddObject(m_lstTmpVertArray);
	}

	// align objects to 128 byte to provide each with an own cacheline since they are updated in parallel
	// for Update Indices
	CStripsInfo										m_StripsInfo _ALIGN(128);
	// for update vectices	
	CTerrainTempDataStorage<SVF_P2S_N4B_C4B_T1F> m_lstTmpVertArray _ALIGN(128);
};

CTerrainUpdateDispatcher::CTerrainUpdateDispatcher()
{
  void* pPoolData = CryModuleMemalign(TempPoolSize, 128);
  if (!pPoolData) 
    CryFatalError(
      "CTerrainUpdateDispatcher::CTerrainUpdateDispatcher() error: "
      "could not allocate %d bytes for temporary pool storage",
      (int32)TempPoolSize);
  m_TempStorage.InitMem(TempPoolSize, reinterpret_cast<uint8*>(pPoolData));
}

CTerrainUpdateDispatcher::~CTerrainUpdateDispatcher()
{
	SyncAllJobs();
  if (m_queuedJobs.size() || m_arrRunningJobs.size()) 
  {
    CryFatalError(
      "CTerrainUpdateDispatcher::~CTerrainUpdateDispatcher(): instance still has jobs "
      "queued while being destructed!\n");
  }
  CryModuleMemalignFree(m_TempStorage.Data());
}

void CTerrainUpdateDispatcher::QueueJob(CTerrainNode* pNode)
{
  if (pNode && !Contains(pNode))
	{		
		if ( !AddJob(pNode, true) )
		{
			// if job submission was unsuccessful, queue the terrain job
      m_queuedJobs.Add(pNode);
		}
	}
}

bool CTerrainUpdateDispatcher::AddJob( CTerrainNode *pNode, bool executeOnSpu )
{
  // 1U<<MML_NOT_SET will generate 0!
  if (pNode->m_cNewGeomMML == MML_NOT_SET) 
    return true; 
  STerrainNodeLeafData *pLeafData = pNode->GetLeafData();
  if (!pLeafData)
    return true;




  const unsigned alignPad = TARGET_DEFAULT_ALIGN - 1;


	// Preallocate enough temp memory to prevent reallocations
	const int nStep = (1<<pNode->m_cNewGeomMML)*CTerrain::GetHeightMapUnitSize();
	const int nSectorSize = CTerrain::GetSectorSize()<<pNode->m_nTreeLevel;  

  int	nNumVerts = (nStep) ? ((nSectorSize/nStep)+1)*((nSectorSize/nStep)+1) : 0;		
  int nNumIdx = (nStep) ? (nSectorSize/nStep)*(nSectorSize/nStep) * 6 : 0;

	// include some of brushes into terrain mesh, for example for nice golf cup  
	if(pNode->m_nTreeLevel == 0)
	{ 
    PodArray<IRenderNode*> lstBrushes; lstBrushes.Clear();
    AABB boxWS = pNode->GetBBox();
    if(Cry3DEngineBase::Get3DEngine()->m_pObjectsTree[pNode->m_nSID])
      Cry3DEngineBase::Get3DEngine()->m_pObjectsTree[pNode->m_nSID]->GetObjectsByType(lstBrushes, eERType_Brush, &boxWS, boxWS.GetRadius()*2.f);
    
    for(int i=0; i<lstBrushes.Count(); i++)
    {
			if(lstBrushes[i]->GetIntegrationType() != eIT_TerrainMesh && lstBrushes[i]->GetIntegrationType() != eIT_TerrainMeshAligned)
				continue;
      executeOnSpu = false; 
      Matrix34A objMat;
      CStatObj * pStatObj = (CStatObj *)lstBrushes[i]->GetEntityStatObj(0,0,&objMat);

      if(IRenderMesh * pRM = pStatObj ? pStatObj->GetRenderMesh() : 0)
      {
        nNumVerts += pRM->GetVerticesCount();
        nNumIdx += pRM->GetIndicesCount();
      }
    }
  }


  if (nNumVerts == 0 || nNumIdx == 0) 
    return true; 

  size_t allocSize = sizeof(CUpdateTerrainTempData); 
  allocSize += alignPad; // padding to meet spu datatransfer alignment requirements

  allocSize += sizeof(uint16)*nNumIdx; 
  allocSize += alignPad; // padding to meet spu datatransfer alignment requirements

	allocSize += sizeof(SVF_P2S_N4B_C4B_T1F)*nNumVerts; 

  uint8* pTempData = m_TempStorage.Allocate<uint8*>(allocSize, __alignof(CUpdateTerrainTempData));
	uint8* orgdata = pTempData;
  if (pTempData) 
  { 
    CUpdateTerrainTempData* pUpdateTerrainTempData = new (pTempData) CUpdateTerrainTempData();
    pTempData += sizeof(CUpdateTerrainTempData);
    pTempData  = (uint8*)((reinterpret_cast<uintptr_t>(pTempData) + alignPad) & ~uintptr_t(alignPad));
		
		pUpdateTerrainTempData->m_StripsInfo.idx_array.PreAllocate(reinterpret_cast<uint16*>(pTempData), nNumIdx);
    pTempData += sizeof(uint16)*nNumIdx;
    pTempData  = (uint8*)((reinterpret_cast<uintptr_t>(pTempData) + alignPad) & ~uintptr_t(alignPad));

		pUpdateTerrainTempData->m_lstTmpVertArray.PreAllocate(reinterpret_cast<SVF_P2S_N4B_C4B_T1F*>(pTempData), nNumVerts);

    pNode->m_pUpdateTerrainTempData = pUpdateTerrainTempData;
      
    PodArray<CTerrainNode*> &lstNeighbourSectors = pLeafData->m_lstNeighbourSectors;
    PodArray<int> &lstNeighbourLods = pLeafData->m_lstNeighbourLods;
    
#ifdef SEG_WORLD
    lstNeighbourSectors.reserve(128U);
    lstNeighbourLods.PreAllocate(128U,128U);
#else
    lstNeighbourSectors.reserve(64U);
    lstNeighbourLods.PreAllocate(64U,64U);
#endif

		// dont run async in case of editor or if we render into shadowmap
		executeOnSpu &= !gEnv->IsEditor();
		executeOnSpu &= !Get3DEngine()->IsRenderingIntoShadowMap();

    if( executeOnSpu)
    {
			ScopedSwitchToGlobalHeap useGlobalHeap;

			TBuildIndicesJob jobIndice;
			jobIndice.SetCacheMode(JobManager::SPUBackend::eCM_32);
			jobIndice.SetClassInstance(pNode);
			jobIndice.RegisterJobState(&pUpdateTerrainTempData->m_JobStateBuildIndices);
			jobIndice.Run();		

			TBuildVerticesJob jobVertices;
			jobVertices.SetClassInstance(pNode);
			jobVertices.SetCacheMode(JobManager::SPUBackend::eCM_32);
			jobVertices.RegisterJobState(&pUpdateTerrainTempData->m_JobStateBuildVertices);
			jobVertices.Run();		
    }
    else
    {
      pNode->BuildIndices_Wrapper();
      pNode->BuildVertices_Wrapper();
    }

    m_arrRunningJobs.Add(pNode);
    return true; 
  }

  return false; 
}

void CTerrainUpdateDispatcher::SyncAllJobs()
{
  FUNCTION_PROFILER_3DENGINE;

  uint32 nNothingQueued = 0;
  do 
  {
    bool bQueued = m_queuedJobs.size() ? false : true; 
    size_t i=0, nEnd=m_queuedJobs.size();
    while (i < nEnd)
    {
      CTerrainNode* pNode = m_queuedJobs[i];
      if (AddJob(pNode, false))
      {
        bQueued = true; 
        m_queuedJobs[i] = m_queuedJobs.Last();
        m_queuedJobs.DeleteLast();
        --nEnd; 
        continue;
      }
      ++i; 
    }

    i=0; nEnd=m_arrRunningJobs.size(); 
    while (i < nEnd)
    {
      CTerrainNode* pNode = m_arrRunningJobs[i]; 
      CUpdateTerrainTempData *pTempStorage = pNode->m_pUpdateTerrainTempData;
      assert(pTempStorage);
      if (!pTempStorage->m_JobStateBuildIndices.IsRunning()&& !pTempStorage->m_JobStateBuildVertices.IsRunning())
      {
        // Finish the render mesh update 
        pNode->RenderSectorUpdate_Finish();
        m_TempStorage.Free(pTempStorage);
        pNode->m_pUpdateTerrainTempData = NULL; 

        // Remove from running list 
        m_arrRunningJobs[i] = m_arrRunningJobs.Last();
        m_arrRunningJobs.DeleteLast();
        --nEnd; 
        continue;
			}
      ++i; 
    }

    if (m_arrRunningJobs.size() == 0 && !bQueued)
      ++nNothingQueued; 
    if (nNothingQueued > 4)
    {
      CryLogAlways("ERROR: not all terrain sector vertex/index update requests could be scheduled");
      break; 
    }
  } while(m_queuedJobs.size() != 0 || m_arrRunningJobs.size() != 0);
}

void CTerrainUpdateDispatcher::GetMemoryUsage(ICrySizer *pSizer) const
{
  pSizer->AddObject(m_TempStorage.Data(), m_TempStorage.MemSize());
	pSizer->AddObject(m_arrRunningJobs);
	pSizer->AddObject(m_queuedJobs);
}

PodArray<uint16> CTerrainNode::m_arrIndices[MAX_SURFACE_TYPES_COUNT][4];
PodArray<SSurfaceType *> CTerrainNode::m_lstReadyTypes(9);

void CTerrainNode::ResetStaticData()
{
	for(int s=0; s<MAX_SURFACE_TYPES_COUNT; s++)
	{
		for(int p=0; p<4; p++)
		{
			stl::free_container(m_arrIndices[s][p]);
		}
	}
}

void CTerrainNode::SetupTexGenParams(SSurfaceType * pSurfaceType, float * pOutParams, uint8 ucProjAxis, bool bOutdoor)
{
	assert(pSurfaceType);

  if(pSurfaceType->fCustomMaxDistance>0)
  {
    pSurfaceType->fMaxMatDistanceZ = pSurfaceType->fMaxMatDistanceXY = pSurfaceType->fCustomMaxDistance;
  }
  else
  {
	  pSurfaceType->fMaxMatDistanceZ	= float(GetFloatCVar(e_TerrainDetailMaterialsViewDistZ)  *Get3DEngine()->m_fTerrainDetailMaterialsViewDistRatio);
	  pSurfaceType->fMaxMatDistanceXY = float(GetFloatCVar(e_TerrainDetailMaterialsViewDistXY) *Get3DEngine()->m_fTerrainDetailMaterialsViewDistRatio);
  }

	// setup projection direction
	if(ucProjAxis == 'X')
	{
		pOutParams[0] = 0;
		pOutParams[1] = 1;
		pOutParams[2] = 0;
		pOutParams[3] = pSurfaceType->fMaxMatDistanceXY;

		pOutParams[4] = 0;
		pOutParams[5] = 0;
		pOutParams[6] = -1;
		pOutParams[7] = 0;
	}
	else if(ucProjAxis =='Y')
	{
		pOutParams[0] = 1;
		pOutParams[1] = 0;
		pOutParams[2] = 0;
		pOutParams[3] = pSurfaceType->fMaxMatDistanceXY;

		pOutParams[4] = 0;
		pOutParams[5] = 0;
		pOutParams[6] = -1;
		pOutParams[7] = 0;
	}
	else // Z
	{
		pOutParams[0] = 1;
		pOutParams[1] = 0;
		pOutParams[2] = 0;
		pOutParams[3] = pSurfaceType->fMaxMatDistanceZ;

		pOutParams[4] = 0;
		pOutParams[5] = -1;
		pOutParams[6] = 0;
		pOutParams[7] = 0;
	}
}

void CTerrainNode::SetupTexGens()
{
	STerrainNodeLeafData * pLeafData = GetLeafData();
	if(!pLeafData)
		return;
	IRenderMesh * & pRenderMesh = pLeafData->m_pRenderMesh;

//	if(pRenderMesh->GetChunks()->Count())
	//	for(int i=TERRAIN_BASE_TEXTURES_NUM; i<MAX_CUSTOM_TEX_BINDS_NUM; i++)
		//	pRenderMesh->GetChunks()->GetAt(0).pRE->m_CustomTexBind[i] = 0;

	pRenderMesh->SetCustomTexID(m_nTexSet.nTex0);

	if (pRenderMesh->GetChunks().Count() && pRenderMesh->GetChunks()[0].pRE)
	{
		if(GetCVars()->e_TerrainTextureDebug == 0)
			pRenderMesh->GetChunks()[0].pRE->m_CustomTexBind[0] = m_nTexSet.nTex0;
		else if(GetCVars()->e_TerrainTextureDebug == 1)
			pRenderMesh->GetChunks()[0].pRE->m_CustomTexBind[0] = m_pTerrain->m_nWhiteTexId;
		else
			pRenderMesh->GetChunks()[0].pRE->m_CustomTexBind[0] = 0;

		pRenderMesh->GetChunks()[0].pRE->m_CustomTexBind[1] = m_nTexSet.nTex1;
	}
}

void CTerrainNode::DrawArray()
{
//	if(m_nSetupTexGensFrameId < GetMainFrameID())
	{
		SetupTexGens();
		//m_nSetupTexGensFrameId = GetMainFrameID();
	}

	IRenderMesh * & pRenderMesh = GetLeafData()->m_pRenderMesh;

	// make renderobject
	CRenderObject * pTerrainRenderObject = GetIdentityCRenderObject();
  if (!pTerrainRenderObject)
    return;
//	if(GetCVars()->e_HwOcclusionCullingObjects)
	//	pTerrainRenderObject->m_ObjFlags |= FOB_NO_Z_PASS;
	pTerrainRenderObject->m_pRenderNode = 0;
	pTerrainRenderObject->m_II.m_AmbColor = Get3DEngine()->GetSkyColor();
  pTerrainRenderObject->m_fDistance = m_arrfDistance[m_nRenderStackLevel];
/*	if(!m_nRenderStackLevel)
	{
		pTerrainRenderObject->m_nScissorX1 = GetCamera().m_ScissorInfo.x1;
		pTerrainRenderObject->m_nScissorY1 = GetCamera().m_ScissorInfo.y1;
		pTerrainRenderObject->m_nScissorX2 = GetCamera().m_ScissorInfo.x2;
		pTerrainRenderObject->m_nScissorY2 = GetCamera().m_ScissorInfo.y2;
	}*/

  pTerrainRenderObject->m_II.m_Matrix.SetIdentity(); 
  Vec3 vOrigin(m_nOriginX, m_nOriginY, 0);
  vOrigin += GetTerrain()->m_arrSegmentOrigns[m_nSID];
  pTerrainRenderObject->m_II.m_Matrix.SetTranslation(vOrigin);
  pTerrainRenderObject->m_ObjFlags |= FOB_TRANS_TRANSLATE;

  uint32 nSectorDLMask = 0;

	const int nEngineFrameID = Cry3DEngineBase::GetFrameID();
  PodArray<CDLight*> * pAffLight = GetAffectingLights(nEngineFrameID);

	// make light mask for terrain sector
	if(!Get3DEngine()->IsRenderingIntoShadowMap())
		nSectorDLMask = Get3DEngine()->BuildLightMask( GetBBox(), pAffLight, NULL, true);

  // pass only sun to base terrain pass
  if(pAffLight && pAffLight->Count() && (pAffLight->GetAt(0)->m_Flags & DLF_SUN))
    pTerrainRenderObject->m_DynLMMask[m_nRenderThreadListID] = 1;

	if(!Get3DEngine()->IsRenderingIntoShadowMap() && GetCVars()->e_Shadows)
		pTerrainRenderObject->m_ObjFlags |= FOB_INSHADOW;

	if(GetTerrain()->m_arrBaseTexInfos[m_nSID].m_hdrDiffTexInfo.dwFlags == TTFHF_AO_DATA_IS_VALID && GetCVars()->e_TerrainNormalMap)
	{ // provide normal map texture for terrain shader
		pTerrainRenderObject->m_nTextureID = m_nTexSet.nTex0;
		//pTerrainRenderObject->m_nTextureID1 = m_nTexSet.nTex1;
    SRenderObjData *pOD = GetRenderer()->EF_GetObjData(pTerrainRenderObject, true);
		pOD->m_fTempVars[0] = m_nTexSet.fTexOffsetX;
		pOD->m_fTempVars[1] = m_nTexSet.fTexOffsetY;
		pOD->m_fTempVars[2] = m_nTexSet.fTexScale;
		pOD->m_fTempVars[3] = m_nTexSet.fTerrainMinZ;
		pOD->m_fTempVars[4] = m_nTexSet.fTerrainMaxZ;
	}

	// render terrain base
  //if(m_arrfDistance[0]<64)
  //  pTerrainRenderObject->m_ObjFlags |= FOB_ONLY_Z_PASS;

 	pRenderMesh->AddRenderElements(GetTerrain()->m_pTerrainEf, pTerrainRenderObject, EFSLIST_GENERAL, 1);

	if(GetCVars()->e_TerrainDetailMaterials /*&& !m_nRenderStackLevel*/ && !Get3DEngine()->IsRenderingIntoShadowMap())
	{
		CRenderObject * pDetailObj = NULL;

		for(int i=0; i<m_lstSurfaceTypeInfo.Count(); i++)
		{
      SSurfaceTypeInfo &SurfaceTypeInfo = m_lstSurfaceTypeInfo[i];
			assert(1 || SurfaceTypeInfo.pSurfaceType->HasMaterial() && SurfaceTypeInfo.HasRM());

      if (!SurfaceTypeInfo.pSurfaceType->HasMaterial() || !SurfaceTypeInfo.HasRM())
        continue;

			if (!pDetailObj)
			{ // make object on first attempt to draw detail layer
				pDetailObj = GetIdentityCRenderObject();
        if (!pDetailObj)
          return;
				pDetailObj->m_pRenderNode = 0;
        pDetailObj->m_ObjFlags |= (((!m_nRenderStackLevel)? FOB_NO_FOG : 0)); // enable fog on recursive rendering (per-vertex)
				pDetailObj->m_II.m_AmbColor = Get3DEngine()->GetSkyColor();
				pDetailObj->m_DynLMMask[m_nRenderThreadListID] = nSectorDLMask;
        pDetailObj->m_fDistance = m_arrfDistance[m_nRenderStackLevel];
        pDetailObj->m_II.m_Matrix = pTerrainRenderObject->m_II.m_Matrix;
        pDetailObj->m_ObjFlags |= FOB_TRANS_TRANSLATE;

				if(GetCVars()->e_Shadows && pDetailObj->m_DynLMMask[m_nRenderThreadListID])
        {
					  pDetailObj->m_ObjFlags |= FOB_INSHADOW;
        }

        if (GetTerrain()->m_arrBaseTexInfos[m_nSID].m_hdrDiffTexInfo.dwFlags == TTFHF_AO_DATA_IS_VALID && GetCVars()->e_TerrainNormalMap)
        { // provide normal map texture for terrain shader
          pDetailObj->m_nTextureID = m_nTexSet.nTex0;
          //pDetailObj->m_nTextureID1 = m_nTexSet.nTex1;
          SRenderObjData *pOD = GetRenderer()->EF_GetObjData(pDetailObj, true);
          pOD->m_fTempVars[0] = m_nTexSet.fTexOffsetX;
          pOD->m_fTempVars[1] = m_nTexSet.fTexOffsetY;
          pOD->m_fTempVars[2] = m_nTexSet.fTexScale;
          pOD->m_fTempVars[3] = m_nTexSet.fTerrainMinZ;
          pOD->m_fTempVars[4] = m_nTexSet.fTerrainMaxZ;
        }
			}

			uint8 szProj[] = "XYZ";
			for (int p=0; p<3; p++)
			{
        if(SSurfaceType * pSurf = SurfaceTypeInfo.pSurfaceType)
				if(IMaterial * pMat = pSurf->GetMaterialOfProjection(szProj[p]))
        {
          pSurf->fMaxMatDistanceZ	  = float(GetFloatCVar(e_TerrainDetailMaterialsViewDistZ) * Get3DEngine()->m_fTerrainDetailMaterialsViewDistRatio);
          pSurf->fMaxMatDistanceXY  = float(GetFloatCVar(e_TerrainDetailMaterialsViewDistXY) *Get3DEngine()->m_fTerrainDetailMaterialsViewDistRatio);

					if(m_arrfDistance[m_nRenderStackLevel]<pSurf->GetMaxMaterialDistanceOfProjection(szProj[p]))
            if(IRenderMesh * pMesh = m_lstSurfaceTypeInfo[i].arrpRM[p])
            {
              AABB aabb;
              pMesh->GetBBox(aabb.min, aabb.max);
              if(GetCamera().IsAABBVisible_F(aabb))
						    if(pMesh->GetVertexContainer() == pRenderMesh && pMesh->GetIndicesCount())
                {
                  if(GetCVars()->e_TerrainBBoxes==2)
                    GetRenderer()->GetIRenderAuxGeom()->DrawAABB(aabb,false,ColorB(255*((m_nTreeLevel&1)>0),255*((m_nTreeLevel&2)>0),0,255),eBBD_Faceted);

							    pMesh->AddRenderElements(pMat, pDetailObj, EFSLIST_TERRAINLAYER, 1);
                }
            }
        }
			}
    }
	}

	// if there are more than 3 detail texture layers - render second pass
	/*  float fZoomFactor = 0.1f+0.9f*(RAD2DEG(GetCamera().GetFov())/90.f);      
	if(bAllowSingePassZ && m_arrSSurfaceType[3] && m_fDistance*fZoomFactor < 90 && !m_nRenderStackLevel)
	pRenderMesh->AddRenderElements( pTerrainRenderObject, 0, 0, EFSLIST_GENERAL, GetTerrain()->m_pMatSecondPass );
	*/
	// terrain fog pass
/*	if( m_pFogVolume && m_pFogVolume->pMat && !nTechniqueID)
	{  
		if(!m_pFogVolume->bOcean || 
			( GetTerrain()->m_pOcean && GetTerrain()->m_pOcean->IsWaterVisible() &&
				GetObjManager()->m_dwRecursionDrawFlags[Cry3DEngineBase::m_nRenderStackLevel] & DLD_TERRAIN_WATER))
		{
			CRenderObject * pTerrainFogCRenderObject = GetRenderer()->EF_GetObject_Temp();
			pTerrainFogCRenderObject->m_ObjFlags |= FOB_FOGPASS;
			pTerrainFogCRenderObject->m_Matrix.SetIdentity();
			if(!m_nRenderStackLevel)
			{
				pTerrainFogCRenderObject->m_nScissorX1 = GetCamera().m_ScissorInfo.x1;
				pTerrainFogCRenderObject->m_nScissorY1 = GetCamera().m_ScissorInfo.y1;
				pTerrainFogCRenderObject->m_nScissorX2 = GetCamera().m_ScissorInfo.x2;
				pTerrainFogCRenderObject->m_nScissorY2 = GetCamera().m_ScissorInfo.y2;
			}
			pRenderMesh->AddRenderElements( pTerrainFogCRenderObject, 0, m_pFogVolume->nRendererVolumeID, EFSLIST_TRANSP | eS_TerrainFogPass );
		}
	}*/
/*
  if(GetCVars()->e_CoverageBufferTerrain && m_arrfDistance[m_nRenderStackLevel] < GetCVars()->e_CoverageBufferTerrain)
  {
    Matrix34 mat;
    mat.SetIdentity();
    Get3DEngine()->GetCoverageBuffer()->AddRenderMesh(pRenderMesh, &mat, pRenderMesh->GetMaterial(), true, false, false);
  }*/
}

// update data in video buffer
void CTerrainNode::UpdateRenderMesh(CStripsInfo * pArrayInfo, bool bUpdateVertices)
{
	FUNCTION_PROFILER_3DENGINE;

	IRenderMesh * & pRenderMesh = GetLeafData()->m_pRenderMesh;
  bool bIndicesUpdated = false; 

	// if changing lod or there is no buffer allocated - reallocate
	if(!pRenderMesh || m_cCurrGeomMML != m_cNewGeomMML || bUpdateVertices)// || GetCVars()->e_TerrainDrawThisSectorOnly)
	{ 
		if(pRenderMesh)
			GetRenderer()->DeleteRenderMesh(pRenderMesh);  

		assert(m_pUpdateTerrainTempData->m_lstTmpVertArray.Count()<65536);

		static PodArray<SPipTangents> lstTangents; lstTangents.Clear();

		if(GetCVars()->e_DefaultMaterial && m_pUpdateTerrainTempData->m_lstTmpVertArray.Count())
		{
			lstTangents.PreAllocate(m_pUpdateTerrainTempData->m_lstTmpVertArray.Count(),m_pUpdateTerrainTempData->m_lstTmpVertArray.Count());

			Vec3 vBinorm(1,0,0); 
			vBinorm.Normalize();

			Vec3 vTang(0,1,0); 
			vTang.Normalize();

			Vec3 vNormal(0,0,1);

			vBinorm = -vNormal.Cross(vTang);
			vTang = vNormal.Cross(vBinorm);

			lstTangents[0].Binormal = Vec4sf(tPackF2B(vBinorm.x),tPackF2B(vBinorm.y),tPackF2B(vBinorm.z), tPackF2B(-1));
			lstTangents[0].Tangent  = Vec4sf(tPackF2B(vTang.x),tPackF2B(vTang.y),tPackF2B(vTang.z), tPackF2B(-1));

			for(int i=1; i<lstTangents.Count(); i++)
				lstTangents[i] = lstTangents[0];
		}

		pRenderMesh = GetRenderer()->CreateRenderMeshInitialized(
			m_pUpdateTerrainTempData->m_lstTmpVertArray.GetElements(), m_pUpdateTerrainTempData->m_lstTmpVertArray.Count(), eVF_P2S_N4B_C4B_T1F, 
      pArrayInfo->idx_array.GetElements(), pArrayInfo->idx_array.Count(),
			prtTriangleList, "TerrainSector", "TerrainSector", eRMT_Static, 1, 
			m_nTexSet.nTex0, NULL, NULL, false, true, lstTangents.Count() ? lstTangents.GetElements() : NULL);    
		AABB boxWS = GetBBox();
    pRenderMesh->SetBBox(boxWS.min, boxWS.max);
    bIndicesUpdated = true; 

    if(GetCVars()->e_TerrainLog==1)
      PrintMessage("RenderMesh created %d", GetSecIndex());
	}

  if (!bUpdateVertices && !GetCVars()->e_TerrainDrawThisSectorOnly)
  {
    assert(pArrayInfo->idx_array.Count()<=(int)pRenderMesh->GetIndicesCount());
  }

  if (!bIndicesUpdated)
    pRenderMesh->UpdateIndices(pArrayInfo->idx_array.GetElements(), pArrayInfo->idx_array.Count(), 0, 0u);

	pRenderMesh->SetChunk(GetTerrain()->m_pTerrainEf,0,pRenderMesh->GetVerticesCount(), 0, pArrayInfo->idx_array.Count(), 1.0f, 0);

	if(pRenderMesh->GetChunks().Count() && pRenderMesh->GetChunks()[0].pRE)
	{
		pRenderMesh->GetChunks()[0].pRE->m_CustomData = GetLeafData()->m_arrTexGen[0];
	}

	assert(pRenderMesh->GetChunks().Count() <= 256); // holes may cause more than 100 chunks
}

void CTerrainNode::UpdateRangeInfoShift()
{
  if(!m_pChilds || !m_nTreeLevel)
    return;

  m_rangeInfo.nUnitBitShift = GetTerrain()->m_nUnitsToSectorBitShift;

  for(int i=0; i<4; i++)
  {
    m_pChilds[i].UpdateRangeInfoShift();
    m_rangeInfo.nUnitBitShift = min(m_pChilds[i].m_rangeInfo.nUnitBitShift, m_rangeInfo.nUnitBitShift);
  }
}

void CTerrainNode::ReleaseHeightMapGeometry(bool bRecursive, const AABB * pBox)
{
  if(pBox && !Overlap::AABB_AABB(*pBox, GetBBox()))
    return;

	for(int i=0; i<m_lstSurfaceTypeInfo.Count(); i++)
		m_lstSurfaceTypeInfo[i].DeleteRenderMeshes(GetRenderer());

	if(GetLeafData() && GetLeafData()->m_pRenderMesh)
	{
		IRenderMesh * & pRenderMesh = GetLeafData()->m_pRenderMesh;

		GetRenderer()->DeleteRenderMesh(pRenderMesh);  
		pRenderMesh = NULL;

		if (GetCVars()->e_TerrainLog==1)
			PrintMessage("RenderMesh unloaded %d", GetSecIndex());
	}

	delete m_pLeafData;
	m_pLeafData = NULL;

//  GetRenderer()->DeleteRenderMesh(m_pCBRenderMesh); m_pCBRenderMesh = NULL;

  if (bRecursive && m_pChilds)
    for(int i=0; i<4; i++)
      m_pChilds[i].ReleaseHeightMapGeometry(bRecursive, pBox);
}

void CTerrainNode::ResetHeightMapGeometry(bool bRecursive, const AABB * pBox)
{
	if(pBox && !Overlap::AABB_AABB(*pBox, GetBBox()))
		return;

	if(m_pLeafData)
		m_pLeafData->m_lstNeighbourSectors.Clear();

	if (bRecursive && m_pChilds)
		for(int i=0; i<4; i++)
			m_pChilds[i].ResetHeightMapGeometry(bRecursive, pBox);
}

// fill vertex buffer
void CTerrainNode::BuildVertices(int nStep, bool bSafetyBorder)
{
	FUNCTION_PROFILER_3DENGINE;

	m_pUpdateTerrainTempData->m_lstTmpVertArray.Clear();

	int nSectorSize = CTerrain::GetSectorSize()<<m_nTreeLevel;

  int nSafetyBorder = 0;//bSafetyBorder ? nStep : 0;

	// keep often used variables on stack
	const uint16	nOriginX = m_nOriginX;
	const uint16	nOriginY = m_nOriginY;
	const int			nSID = m_nSID;
	const int			nTerrainSize  =   CTerrain::GetTerrainSize();
	const int     iLookupRadius = 2*CTerrain::GetHeightMapUnitSize();
	CTerrain*			pTerrain = GetTerrain();

	for( int x=nOriginX-nSafetyBorder; x<=nOriginX+nSectorSize+nSafetyBorder; x+=nStep)
  {
		for( int y=nOriginY-nSafetyBorder; y<=nOriginY+nSectorSize+nSafetyBorder; y+=nStep)
		{
      int _x = CLAMP(x, nOriginX, nOriginX+nSectorSize);
      int _y = CLAMP(y, nOriginY, nOriginY+nSectorSize);
      float _z = pTerrain->GetZ(_x,_y, nSID);

      // close gaps on border of terrain for shadow mapping
//      if(	_x <= 0 || _x >= pTerrain->GetTerrainSize() ||
  //      _y <= 0 || _y >= pTerrain->GetTerrainSize())
    //    _z = 0;

      // safety borders are used for cbuffer, note: holes not supported here
//      if(	x != _x || y != _y )
  //      _z = max(_z-2.f*nStep, 0.f);

			SVF_P2S_N4B_C4B_T1F vert;

			vert.xy = CryHalf2((float)(_x-nOriginX), (float)(_y-nOriginY));
			vert.z  = _z;
 

			// calculate surface normal
#ifdef SEG_WORLD
			bool bOutOfBound = false;
			bOutOfBound = ((x+iLookupRadius)<nTerrainSize && x> iLookupRadius) ? false : true;
			float sx = pTerrain->GetZ(x+iLookupRadius,y, nSID, bOutOfBound  ) - pTerrain->GetZ(x-iLookupRadius,y, nSID, bOutOfBound  );

			bOutOfBound = ((y+iLookupRadius)<nTerrainSize && y> iLookupRadius) ? false : true;
			float sy = pTerrain->GetZ(x  ,y+iLookupRadius, nSID, bOutOfBound) - pTerrain->GetZ(x  ,y-iLookupRadius, nSID, bOutOfBound);
#else
			float sx;
			if((x+iLookupRadius)<nTerrainSize && x> iLookupRadius)
				sx = pTerrain->GetZ(x+iLookupRadius,y, nSID  ) - pTerrain->GetZ(x-iLookupRadius,y, nSID  );
			else
				sx = 0;

			float sy;
			if((y+iLookupRadius)<nTerrainSize && y> iLookupRadius)
				sy = pTerrain->GetZ(x  ,y+iLookupRadius, nSID) - pTerrain->GetZ(x  ,y-iLookupRadius, nSID);
			else
				sy = 0;
#endif
			// z component of normal will be used as point brightness ( for burned terrain )
			Vec3 vNorm(-sx, -sy, iLookupRadius*2.0f);
			vNorm.Normalize();

			vert.normal.bcolor[0] = (byte)(vNorm[0] * 127.5f + 128.0f);
      vert.normal.bcolor[1] = (byte)(vNorm[1] * 127.5f + 128.0f);
      vert.normal.bcolor[2] = (byte)(vNorm[2] * 127.5f + 128.0f);
      vert.normal.bcolor[3] = 255;
      SwapEndian(vert.normal.dcolor, eLittleEndian);

			uint8 ucSurfaceTypeID = pTerrain->GetSurfaceTypeID(x,y, nSID);
			if(ucSurfaceTypeID == STYPE_HOLE)
			{ // in case of hole - try to find some valid surface type around
				for(int i=-nStep; i<=nStep && (ucSurfaceTypeID == STYPE_HOLE); i+=nStep)
					for(int j=-nStep; j<=nStep && (ucSurfaceTypeID == STYPE_HOLE); j+=nStep)
						ucSurfaceTypeID = pTerrain->GetSurfaceTypeID(x+i,y+j, nSID);
			}

      ucSurfaceTypeID = min((int)ucSurfaceTypeID, (int)(MAX_SURFACE_TYPES_COUNT-1) );

			vert.color.bcolor[0] = 255;
			vert.color.bcolor[1] = ucSurfaceTypeID;
			vert.color.bcolor[2] = 255;
			vert.color.bcolor[3] = 255;
			SwapEndian(vert.color.dcolor, eLittleEndian);

			m_pUpdateTerrainTempData->m_lstTmpVertArray.Add(vert);
		}
  }

	// include some of brushes into terrain mesh, for example for nice golf cup
	if( 0 && m_nTreeLevel == 0) //TODO: NOT CURRENTLY SUPPORTED DUE TO NEW INTEGER DATA IMPLEMENTATION
	{ 




    PodArray<IRenderNode*> lstBrushes; lstBrushes.Clear();
    AABB boxWS = GetBBox();
    if(Get3DEngine()->m_pObjectsTree[nSID])
      Get3DEngine()->m_pObjectsTree[nSID]->GetObjectsByType(lstBrushes, eERType_Brush, &boxWS, boxWS.GetRadius()*2.f);

    for(int i=0; i<lstBrushes.Count(); i++)
    {
			if(lstBrushes[i]->GetIntegrationType() != eIT_TerrainMesh && lstBrushes[i]->GetIntegrationType() != eIT_TerrainMeshAligned)
				continue;

      Matrix34A objMat;
      CStatObj * pStatObj = (CStatObj *)lstBrushes[i]->GetEntityStatObj(0,0,&objMat);

      if(IRenderMesh * pRM = pStatObj ? pStatObj->GetRenderMesh() : 0)
      {
				pRM->LockForThreadAccess();
        int nPosStride=0;
        const byte * pPos = pRM->GetPosPtr(nPosStride, FSL_READ);
        const int nVertCount = pRM->GetVerticesCount();

        int nTangStride=0;
        int nBNormStride=0;
        byte * pTang = 0;
        byte * pBNorm = 0;
				if (pRM->GetVertexFormat() != eVF_P2S_N4B_C4B_T1F)
        {
          pTang = pRM->GetTangentPtr(nTangStride, FSL_READ);
          pBNorm = pRM->GetBinormalPtr(nBNormStride, FSL_READ);
        }

        for(int v=0; v<nVertCount; v++)
        {
          Vec3 vPosOS; memcpy(&vPosOS, &pPos[nPosStride*v], sizeof(vPosOS));
          Vec3 vPos = objMat.TransformPoint(vPosOS);

					if(lstBrushes[i]->GetIntegrationType() == eIT_TerrainMeshAligned)
						vPos.z = pTerrain->GetZApr(vPos.x,vPos.y, false, nSID) + (vPos.z - objMat.GetTranslation().z);

          SVF_P2S_N4B_C4B_T1F vert;   
	//				vert.xyz = vPos - Vec3(nOriginX, nOriginY, 0);

        Vec3 vNorm(0, 0, 1.f);

          if(pTang && pBNorm)
          {
            Vec4sf Tangent; 
            memcpy(&Tangent, &pTang[nTangStride*v], sizeof(Tangent));
            Vec4sf Binormal; 
            memcpy(&Binormal, &pBNorm[nBNormStride*v], sizeof(Binormal));





            Vec3 tangent	= Vec3(tPackB2F(Tangent.x), tPackB2F(Tangent.y), tPackB2F(Tangent.z)); float tw = tPackB2F(Binormal.w);
            Vec3 binormal	= Vec3(tPackB2F(Binormal.x), tPackB2F(Binormal.y), tPackB2F(Binormal.z)); float bw = tPackB2F(Binormal.w);


            tangent.Normalize(); 
            binormal.Normalize(); 
            vNorm = tangent.Cross(binormal);
						vNorm*=tw;
          }

          vNorm.Normalize();

          vert.normal.bcolor[0] = (byte)(vNorm[0] * 127.5f + 128.0f);
          vert.normal.bcolor[1] = (byte)(vNorm[1] * 127.5f + 128.0f);
          vert.normal.bcolor[2] = (byte)(vNorm[2] * 127.5f + 128.0f);
          vert.normal.bcolor[3] = 255;
          SwapEndian(vert.normal.dcolor, eLittleEndian);

					//TODO: NOT CURRENTLY SUPPORTED DUE TO NEW INTEGER DATA IMPLEMENTATION
					uint8 ucSurfaceTypeID = 0;//pTerrain->GetSurfaceTypeID((int)vert.xyz.x, (int)vert.xyz.y, nSID);
          if(ucSurfaceTypeID == STYPE_HOLE)
          { // in case of hole - try to find some valid surface type around
            for(int ii=-nStep; ii<=nStep && (ucSurfaceTypeID == STYPE_HOLE); ii+=nStep)
              for(int j=-nStep; j<=nStep && (ucSurfaceTypeID == STYPE_HOLE); j+=nStep)
								//TODO: NOT CURRENTLY SUPPORTED DUE TO NEW INTEGER DATA IMPLEMENTATION
								ucSurfaceTypeID = 0;//pTerrain->GetSurfaceTypeID((int)vert.xyz.x+ii, (int)vert.xyz.y+j, nSID);
          }

          ucSurfaceTypeID = min((int)ucSurfaceTypeID, (int)(MAX_SURFACE_TYPES_COUNT-1));

          vert.color.bcolor[0] = 255;
          vert.color.bcolor[1] = ucSurfaceTypeID;
          vert.color.bcolor[2] = 255;
          vert.color.bcolor[3] = 255;
          SwapEndian(vert.color.dcolor, eLittleEndian);

          m_pUpdateTerrainTempData->m_lstTmpVertArray.Add(vert);
        }

				if (pTang && pBNorm) pRM->UnlockStream(VSF_TANGENTS); 
				pRM->UnlockStream(VSF_GENERAL);
				pRM->UnLockForThreadAccess();
      }
    }

  }
}

namespace Util
{
	// small util function to improve the simulation time, by using no-inline on SPU
	SPU_NO_INLINE void AddNeighbourNode(PodArray<CTerrainNode*> *plstNeighbourSectors, CTerrainNode * pNode)
	{
		// todo: cache this list, it is always the same
		if(pNode && plstNeighbourSectors->Find(pNode)<0)
			plstNeighbourSectors->Add(pNode);
	}	
}

SPU_NO_INLINE void CTerrainNode::AddIndexAliased(int _x, int _y, int _step, int nSectorSize, PodArray<CTerrainNode*> * plstNeighbourSectors, CStripsInfo * pArrayInfo)
{
	int nAliasingX=1, nAliasingY=1;
	int nShiftX=0, nShiftY=0;
	
	CTerrain *pTerrain = GetTerrain();
	int nHeightMapUnitSize = CTerrain::GetHeightMapUnitSize();

	IF(_x && _x<nSectorSize && plstNeighbourSectors, true)
	{
		IF(_y==0, false)
		{
			if(CTerrainNode * pNode = pTerrain->GetSecInfo(m_nOriginX+_x, m_nOriginY+_y-_step, m_nSID))
			{
        int nAreaMML = pNode->GetAreaLOD();
				if(nAreaMML != MML_NOT_SET)
				{
					nAliasingX = nHeightMapUnitSize*(1<<nAreaMML);
					nShiftX = nAliasingX/4;
				}
				Util::AddNeighbourNode(plstNeighbourSectors,pNode);				
			}
		}
		else IF(_y==nSectorSize, false)
		{
			if(CTerrainNode * pNode = pTerrain->GetSecInfo(m_nOriginX+_x, m_nOriginY+_y+_step, m_nSID))
			{
        int nAreaMML = pNode->GetAreaLOD();
				if(nAreaMML != MML_NOT_SET)
				{
					nAliasingX = nHeightMapUnitSize*(1<<nAreaMML);
					nShiftX = nAliasingX/2;
				}
				Util::AddNeighbourNode(plstNeighbourSectors,pNode);				
			}
		}
	}

	IF(_y && _y<nSectorSize && plstNeighbourSectors, true)
	{
		IF(_x==0 ,false)
		{
			if(CTerrainNode * pNode = pTerrain->GetSecInfo(m_nOriginX+_x-_step, m_nOriginY+_y, m_nSID))
			{
        int nAreaMML = pNode->GetAreaLOD();
				if(nAreaMML != MML_NOT_SET)
				{
					nAliasingY = nHeightMapUnitSize*(1<<nAreaMML);
					nShiftY = nAliasingY/4;
				}
				Util::AddNeighbourNode(plstNeighbourSectors,pNode);				
			}
		}
		else IF(_x==nSectorSize,false)
		{
			if(CTerrainNode * pNode = pTerrain->GetSecInfo(m_nOriginX+_x+_step, m_nOriginY+_y, m_nSID))
			{
        int nAreaMML = pNode->GetAreaLOD();
				if(nAreaMML != MML_NOT_SET)
				{
					nAliasingY = nHeightMapUnitSize*(1<<nAreaMML);
					nShiftY = nAliasingY/2;
				}
				Util::AddNeighbourNode(plstNeighbourSectors,pNode);				
			}
		}
	}

	int XA = nAliasingX ? (_x+nShiftX)/nAliasingX*nAliasingX : _x;
	int YA = nAliasingY ? (_y+nShiftY)/nAliasingY*nAliasingY : _y;

	assert(XA>=0 && XA<=nSectorSize);
	assert(YA>=0 && YA<=nSectorSize);

	pArrayInfo->AddIndex(XA,YA,_step,nSectorSize);
}


SPU_NO_INLINE void CTerrainNode::BuildIndices(CStripsInfo & si, PodArray<CTerrainNode*> * pNeighbourSectors, bool bSafetyBorder)
{
  FUNCTION_PROFILER_3DENGINE;

  // 1<<MML_NOT_SET will generate 0; 
  if (m_cNewGeomMML == MML_NOT_SET) 
    return; 

  int nStep = (1<<m_cNewGeomMML)*CTerrain::GetHeightMapUnitSize();
  int nSectorSize = CTerrain::GetSectorSize()<<m_nTreeLevel;  

  int nSafetyBorder = bSafetyBorder ? nStep : 0;

  int nSectorSizeSB = nSectorSize+nSafetyBorder*2;

  si.Clear();

	CStripsInfo *pSI = &si;
	CTerrain *pTerrain = GetTerrain();

  int nHalfStep = nStep/2;

  // add non borders
  for( int x=0; x<nSectorSizeSB; x+=nStep )
  {   
    for( int y=0; y<nSectorSizeSB; y+=nStep )
    {
      bool bBorder = x<=0 || y<=0 || x>=(nSectorSizeSB-nStep) || y>=(nSectorSizeSB-nStep);
      if(bBorder)
        continue;

      if(!m_bHasHoles || /*m_cNewGeomMML || */!pTerrain->GetHole(m_nOriginX+x+nHalfStep,m_nOriginY+y+nHalfStep, m_nSID))
      {
				// prevent surface interpolation over long edge
				bool bFlipTris = false;
				int nType10 = pTerrain->GetSurfaceTypeID(m_nOriginX+x+nStep,m_nOriginY+y, 0);
				int nType01 = pTerrain->GetSurfaceTypeID(m_nOriginX+x,m_nOriginY+y+nStep, 0);
				if( nType10 != nType01 )
				{
					int nType00 = pTerrain->GetSurfaceTypeID(m_nOriginX+x,m_nOriginY+y, 0);
					int nType11 = pTerrain->GetSurfaceTypeID(m_nOriginX+x+nStep,m_nOriginY+y+nStep, 0);
					if( (nType10 == nType00 && nType10 == nType11) || (nType01 == nType00 && nType01 == nType11) )
						bFlipTris=true;
				}

				if(bFlipTris)
				{
					AddIndexAliased(x+nStep ,y        ,nStep,nSectorSizeSB,pNeighbourSectors,pSI);
					AddIndexAliased(x+nStep ,y+nStep  ,nStep,nSectorSizeSB,pNeighbourSectors,pSI);
					AddIndexAliased(x       ,y        ,nStep,nSectorSizeSB,pNeighbourSectors,pSI);

					AddIndexAliased(x       ,y        ,nStep,nSectorSizeSB,pNeighbourSectors,pSI);
					AddIndexAliased(x+nStep ,y+nStep  ,nStep,nSectorSizeSB,pNeighbourSectors,pSI);
					AddIndexAliased(x       ,y+nStep  ,nStep,nSectorSizeSB,pNeighbourSectors,pSI);
				}
				else
				{
					AddIndexAliased(x       ,y        ,nStep,nSectorSizeSB,pNeighbourSectors,pSI);
					AddIndexAliased(x+nStep ,y        ,nStep,nSectorSizeSB,pNeighbourSectors,pSI);
					AddIndexAliased(x       ,y+nStep  ,nStep,nSectorSizeSB,pNeighbourSectors,pSI);

					AddIndexAliased(x+nStep ,y        ,nStep,nSectorSizeSB,pNeighbourSectors,pSI);
					AddIndexAliased(x+nStep ,y+nStep  ,nStep,nSectorSizeSB,pNeighbourSectors,pSI);
					AddIndexAliased(x       ,y+nStep  ,nStep,nSectorSizeSB,pNeighbourSectors,pSI);
				}
      }
    }
  }

  si.nNonBorderIndicesCount = si.idx_array.Count();

  // add borders
  for( int x=0; x<nSectorSizeSB; x+=nStep )
  {   
    for( int y=0; y<nSectorSizeSB; y+=nStep )
    {
      bool bBorder = x<=0 || y<=0 || x>=(nSectorSizeSB-nStep) || y>=(nSectorSizeSB-nStep);
      if(!bBorder)
        continue;

      if(!m_bHasHoles || /*m_cNewGeomMML || */!pTerrain->GetHole(m_nOriginX+x+nHalfStep,m_nOriginY+y+nHalfStep, m_nSID))
      {
				if(pTerrain->IsMeshQuadFlipped(m_nOriginX+x, m_nOriginY+y, nStep, 0))
				{
					AddIndexAliased(x+nStep ,y        ,nStep,nSectorSizeSB,pNeighbourSectors,pSI);
					AddIndexAliased(x+nStep ,y+nStep  ,nStep,nSectorSizeSB,pNeighbourSectors,pSI);
					AddIndexAliased(x       ,y        ,nStep,nSectorSizeSB,pNeighbourSectors,pSI);

					AddIndexAliased(x       ,y        ,nStep,nSectorSizeSB,pNeighbourSectors,pSI);
					AddIndexAliased(x+nStep ,y+nStep  ,nStep,nSectorSizeSB,pNeighbourSectors,pSI);
					AddIndexAliased(x       ,y+nStep  ,nStep,nSectorSizeSB,pNeighbourSectors,pSI);
				}
				else
				{
					AddIndexAliased(x       ,y        ,nStep,nSectorSizeSB,pNeighbourSectors,pSI);
					AddIndexAliased(x+nStep ,y        ,nStep,nSectorSizeSB,pNeighbourSectors,pSI);
					AddIndexAliased(x       ,y+nStep  ,nStep,nSectorSizeSB,pNeighbourSectors,pSI);

					AddIndexAliased(x+nStep ,y        ,nStep,nSectorSizeSB,pNeighbourSectors,pSI);
					AddIndexAliased(x+nStep ,y+nStep  ,nStep,nSectorSizeSB,pNeighbourSectors,pSI);
					AddIndexAliased(x       ,y+nStep  ,nStep,nSectorSizeSB,pNeighbourSectors,pSI);
				}
      }
    }
  }


	// include some of brushes into terrain mesh, for example for nice golf cup
	if(m_nTreeLevel == 0)
  { 




    PodArray<IRenderNode*> lstBrushes; lstBrushes.Clear();
    AABB boxWS = GetBBox();

    if(Get3DEngine()->m_pObjectsTree[m_nSID])
      Get3DEngine()->m_pObjectsTree[m_nSID]->GetObjectsByType(lstBrushes, eERType_Brush, &boxWS, boxWS.GetRadius()*2.f);

    int nSectorSizeUnits = (CTerrain::GetSectorSize()<<m_nTreeLevel) / nStep;

    int nVertOffset = (nSectorSizeUnits+1)*(nSectorSizeUnits+1);

    for(int i=0; i<lstBrushes.Count(); i++)
    {
      if(lstBrushes[i]->GetIntegrationType() != eIT_TerrainMesh && lstBrushes[i]->GetIntegrationType() != eIT_TerrainMeshAligned)
        continue;

      Matrix34A objMat;
      CStatObj * pStatObj = (CStatObj *)lstBrushes[i]->GetEntityStatObj(0,0,&objMat);

      if(IRenderMesh * pRM = pStatObj ? pStatObj->GetRenderMesh() : 0)
      {
				pRM->LockForThreadAccess();
        int nIndCount = pRM->GetIndicesCount();
        const uint16 * pIndices = pRM->GetIndexPtr(FSL_READ);
				assert(pIndices);
				if(!pIndices)
					continue;
        const int nVertCount = pRM->GetVerticesCount();

        for(int n=0; n<nIndCount; n++)
        {
          int id = pIndices[n] + nVertOffset;
          m_pUpdateTerrainTempData->m_StripsInfo.idx_array.Add(id);
        }

        nVertOffset += nVertCount;

				pRM->UnlockIndexStream(); 
				pRM->UnLockForThreadAccess();
      }
    }

  }
}

// entry point
bool CTerrainNode::RenderSector()
{
	FUNCTION_PROFILER_3DENGINE;

	//m_nNodeRenderLastFrameId = GetMainFrameID();

	assert(m_nRenderStackLevel || m_cNewGeomMML<GetTerrain()->m_nUnitsToSectorBitShift);

	STerrainNodeLeafData * pLeafData = GetLeafData();

	// detect if any neighbors switched lod since previous frame
	bool bNeighbourChanged = false;

	if(!Get3DEngine()->IsRenderingIntoShadowMap())
  {
	  for(int i=0; i<pLeafData->m_lstNeighbourSectors.Count(); i++)
	  {
			if(!pLeafData->m_lstNeighbourSectors[i])
				continue;
      int nNeighbourNewMML = pLeafData->m_lstNeighbourSectors[i]->GetAreaLOD();
      if (nNeighbourNewMML == MML_NOT_SET) 
        continue;
		  if( nNeighbourNewMML != pLeafData->m_lstNeighbourLods[i] && (nNeighbourNewMML>m_cNewGeomMML || pLeafData->m_lstNeighbourLods[i]>m_cNewGeomMML) )
		  {
			  bNeighbourChanged = true;
			  break;
		  }
	  }
  }

	IRenderMesh * & pRenderMesh = GetLeafData()->m_pRenderMesh;

	bool bDetailLayersReady = Get3DEngine()->IsRenderingIntoShadowMap() ||
		!m_lstSurfaceTypeInfo.Count() ||
		!GetCVars()->e_TerrainDetailMaterials;
  for (int i=0; i<m_lstSurfaceTypeInfo.Count() && !bDetailLayersReady; ++i)
  {
		bDetailLayersReady = 
      m_lstSurfaceTypeInfo[i].HasRM() || 
      m_lstSurfaceTypeInfo[i].pSurfaceType ||
      !m_lstSurfaceTypeInfo[i].pSurfaceType->HasMaterial();
  }

	// just draw if everything is up to date already
	if(!GetLeafData())
		return false;

	if(pRenderMesh && GetCVars()->e_TerrainDrawThisSectorOnly<2 && bDetailLayersReady)
	{
		if( m_nRenderStackLevel || ( m_cCurrGeomMML == m_cNewGeomMML && !bNeighbourChanged ))
		{ 
			DrawArray(); 
			return true; 
		}
	}

	if(m_nRenderStackLevel)
		if(pRenderMesh)
			return true;

  return false; 
}
void CTerrainNode::RenderSectorUpdate_Finish()
{
	assert( m_pUpdateTerrainTempData != NULL );
	if(  m_nRenderStackLevel == 0 )
	{				
		FRAME_PROFILER( "Sync_UpdateTerrain", GetSystem(), PROFILE_3DENGINE );
		gEnv->GetJobManager()->WaitForJob(m_pUpdateTerrainTempData->m_JobStateBuildIndices);
		gEnv->GetJobManager()->WaitForJob(m_pUpdateTerrainTempData->m_JobStateBuildVertices);

		if( m_pUpdateTerrainTempData->m_StripsInfo.idx_array.Count() == 0 )
			m_pUpdateTerrainTempData->m_lstTmpVertArray.Clear();
	}

	// if an allocation failed, destroy the temp data and return
	if( m_pUpdateTerrainTempData->m_StripsInfo.idx_array.MemorySize() == 0 || 
			m_pUpdateTerrainTempData->m_lstTmpVertArray.MemorySize() == 0 )
		return;
	
	STerrainNodeLeafData * pLeafData = GetLeafData();
	if(!pLeafData)
		return;

	IRenderMesh * & pRenderMesh = pLeafData->m_pRenderMesh;

	UpdateRenderMesh(&m_pUpdateTerrainTempData->m_StripsInfo, !m_bUpdateOnlyBorders);

	m_cCurrGeomMML = m_cNewGeomMML;

	// update detail layers indices
	if(GetCVars()->e_TerrainDetailMaterials ) //&& (!m_nRenderStackLevel || GetCVars()->e_TerrainDetailMaterials))
	{
		// build all indices
    GenerateIndicesForAllSurfaces(pRenderMesh, m_bUpdateOnlyBorders, pLeafData->m_arrpNonBorderIdxNum, m_pUpdateTerrainTempData->m_StripsInfo.nNonBorderIndicesCount, 0, m_nSID, m_pUpdateTerrainTempData);

    m_lstReadyTypes.Clear(); // protection from duplications in palette of types

		uint8 szProj[] = "XYZ";
		for(int i=0; i<m_lstSurfaceTypeInfo.Count(); i++)
		{
      if(SSurfaceType * pSurfaceType = m_lstSurfaceTypeInfo[i].pSurfaceType)
      {
        if(m_lstReadyTypes.Find(pSurfaceType)<0)
        {
          bool b3D = pSurfaceType->IsMaterial3D();
          for(int p=0; p<3; p++)
          {
            if(b3D || pSurfaceType->GetMaterialOfProjection(szProj[p]))
            {
              int nProjId = b3D ? p : 3;
              PodArray<unsigned short> & lstIndices = m_arrIndices[pSurfaceType->ucThisSurfaceTypeId][nProjId];

							if(!m_bUpdateOnlyBorders && m_lstSurfaceTypeInfo[i].arrpRM[p] && (lstIndices.Count() != m_lstSurfaceTypeInfo[i].arrpRM[p]->GetIndicesCount()))
							{
								GetRenderer()->DeleteRenderMesh(m_lstSurfaceTypeInfo[i].arrpRM[p]);
								m_lstSurfaceTypeInfo[i].arrpRM[p] = NULL;
							}

							if(lstIndices.Count())
								UpdateSurfaceRenderMeshes(pRenderMesh, pSurfaceType, m_lstSurfaceTypeInfo[i].arrpRM[p], p, lstIndices, "TerrainMaterialLayer", m_bUpdateOnlyBorders, pLeafData->m_arrpNonBorderIdxNum[pSurfaceType->ucThisSurfaceTypeId][nProjId]);             

              if(!b3D)
                break;
            }
          }
          m_lstReadyTypes.Add(pSurfaceType);
        }
      }
		}
	}

	DrawArray();
}

// function used as SPU entry point for BuildIndices
#if !defined(CRYCG_CM)
SPU_ENTRY(Terrain_BuildIndices)
#endif
void CTerrainNode::BuildIndices_Wrapper()
{
	CUpdateTerrainTempData *pUpdateTerrainTempData = m_pUpdateTerrainTempData;

	// don't try to create terrain data if an allocation failed
	if( pUpdateTerrainTempData->m_StripsInfo.idx_array.MemorySize() == 0 ) return;






	STerrainNodeLeafData *pLeafData = GetLeafData();
	PodArray<CTerrainNode*> &lstNeighbourSectors = pLeafData->m_lstNeighbourSectors;
	PodArray<int> &lstNeighbourLods = pLeafData->m_lstNeighbourLods;
	lstNeighbourSectors.Clear();
	BuildIndices(pUpdateTerrainTempData->m_StripsInfo, &lstNeighbourSectors, false);

	// remember neighbor LOD's
	lstNeighbourLods.resize(lstNeighbourSectors.size());
	for(int i=0; i<lstNeighbourSectors.Count(); i++)
  {
    int nNeighbourMML = lstNeighbourSectors[i]->GetAreaLOD(); 
#if !defined(PS3) // ChrisR: disabled at the moment
    lstNeighbourLods[i] = nNeighbourMML;


#endif 
  }





}

// function used as SPU entry point for BuildVertices
#if !defined(CRYCG_CM)
SPU_ENTRY(Terrain_BuildVertices)
#endif
void CTerrainNode::BuildVertices_Wrapper()
{
	// don't try to create terrain data if an allocation failed
	if( m_pUpdateTerrainTempData->m_lstTmpVertArray.MemorySize() == 0 )
		return;

	STerrainNodeLeafData * pLeafData = GetLeafData();
	if(!pLeafData)
		return;






	IRenderMesh * & pRenderMesh = pLeafData->m_pRenderMesh;

	int nOldSrcCount = -1;
	if (pRenderMesh)
		nOldSrcCount = pRenderMesh->GetIndicesCount();

  // 1U<<MML_NOT_SET will generate zero 
  if (m_cNewGeomMML == MML_NOT_SET)
    return; 

	int nStep = (1<<m_cNewGeomMML)*CTerrain::GetHeightMapUnitSize();
	int nSectorSize = CTerrain::GetSectorSize()<<m_nTreeLevel;
	assert(nStep && nStep<=nSectorSize);
	if(nStep > nSectorSize)
		nStep = nSectorSize;

	// this could cost performace? to allow parallel execution, we cannot check for unchanged indices
#if 1 // defined(__SPU__) // for SPU, always execute BuildVertices, since BuildIndices is run in parallel
	BuildVertices(nStep, false);
	m_bUpdateOnlyBorders = false;
#else
	// update vertices if needed
	int nVertsNumRequired = (nSectorSize/nStep)*(nSectorSize/nStep)+(nSectorSize/nStep)+(nSectorSize/nStep)+1;
	if(!pRenderMesh || m_cCurrGeomMML != m_cNewGeomMML || nVertsNumRequired != pRenderMesh->GetVerticesCount() || m_bEditor || !m_cNewGeomMML)
	{
		if(m_pUpdateTerrainTempData->m_StripsInfo.idx_array.Count())
			BuildVertices(nStep, false);
		else
			m_pUpdateTerrainTempData->m_lstTmpVertArray.Clear();   		

		m_bUpdateOnlyBorders = false;
	}
	else
	{
		m_bUpdateOnlyBorders = true;
	}
#endif





}

/*void CTerrainNode::GenerateIndicesForQuad(IRenderMesh * pRM, Vec3 vBoxMin, Vec3 vBoxMax, PodArray<uint16> & dstIndices)
{
	dstIndices.Clear();

	int nSrcCount=0;
	uint16 * pSrcInds = pRM->GetIndices(&nSrcCount);

	int nPosStride=0;
	byte * pPos = pRM->GetStridedPosPtr(nPosStride);

	for(int i=0; i<(*pRM->GetChunks()).Count(); i++)
	{
		if (!(*pRM->GetChunks())[i].pRE)
			continue;

		CRenderChunk * pChunk = &(*pRM->GetChunks())[i];

		for (int j=pChunk->nFirstIndexId; (j+2)<pChunk->nNumIndices+pChunk->nFirstIndexId; j+=3)
		{
			int nIndex0 = pSrcInds[j+0];
			int nIndex1 = pSrcInds[j+1];
			int nIndex2 = pSrcInds[j+2];

			assert(nIndex0>=0 && nIndex0<pRM->GetVertCount());
			assert(nIndex1>=0 && nIndex1<pRM->GetVertCount());
			assert(nIndex2>=0 && nIndex2<pRM->GetVertCount());

			Vec3 & vPos0 = *(Vec3*)&pPos[nIndex0*nPosStride];
			Vec3 & vPos1 = *(Vec3*)&pPos[nIndex1*nPosStride];
			Vec3 & vPos2 = *(Vec3*)&pPos[nIndex2*nPosStride];

			if(nIndex0 != nIndex1 && nIndex1 != nIndex2 && nIndex2 != nIndex0)
				if(Overlap::AABB_Triangle(AABB(vBoxMin,vBoxMax),vPos0,vPos1,vPos2))
			{
				if(j&1)
				{
					dstIndices.Add(nIndex0);
					dstIndices.Add(nIndex2);
					dstIndices.Add(nIndex1);
				}
				else
				{
					dstIndices.Add(nIndex0);
					dstIndices.Add(nIndex1);
					dstIndices.Add(nIndex2);
				}
			}
		}
	}
}*/

int GetVecProjectId(const Vec3 & vNorm, float zAngle)
{
	Vec3 vNormAbs = vNorm.abs();

	if( vNormAbs.z > zAngle ) return 2;
	else                      return vNormAbs.x > vNormAbs.y ? 0 : 1;
}

void CTerrainNode::GenerateIndicesForAllSurfaces(IRenderMesh * pRM, bool bOnlyBorder, short arrpNonBorderIdxNum[MAX_SURFACE_TYPES_COUNT][4], int nBorderStartIndex, SSurfaceTypeInfo * pSurfaceTypeInfos, int nSID, CUpdateTerrainTempData *pUpdateTerrainTempData)
{
	FUNCTION_PROFILER_3DENGINE;

	byte arrMat3DFlag[MAX_SURFACE_TYPES_COUNT];

	enum { ARR_INDICES_0_BUFFER_SIZE = 8192 * 3, ARR_INDICES_BUFFER_SIZE = 2048 * 3 };

	m_arrIndices[0][3].reserve(ARR_INDICES_0_BUFFER_SIZE);

	for(int s=0; s<MAX_SURFACE_TYPES_COUNT; s++)
	{
		if (s < 10)
		{
			m_arrIndices[s][3].reserve(ARR_INDICES_BUFFER_SIZE);
		}

		for(int p=0; p<4; p++)
    {
      m_arrIndices[s][p].Clear();

      if(!bOnlyBorder)
        arrpNonBorderIdxNum[s][p] = -1;
    }

    arrMat3DFlag[s] = 0;

    if(pSurfaceTypeInfos)
    {
      if(pSurfaceTypeInfos[s].pSurfaceType)
        arrMat3DFlag[s] = pSurfaceTypeInfos[s].pSurfaceType->IsMaterial3D();
    }
    else if(s<GetTerrain()->GetSurfaceTypeCount(nSID))
    {
      arrMat3DFlag[s] = GetTerrain()->GetSurfaceTypes(nSID)[s].IsMaterial3D();
    }
	}

	int nSrcCount =0;
	uint16 *pSrcInds = NULL;		
	byte *pPosPtr = NULL;	
	int nColorStride = 0;
	byte *pColor = NULL;
	int nNormStride =  0;	
	byte *pNormB = NULL;

	bool useMesh = false;

	// when possible, use the already on ppu computed data instead of going through the rendermesh
	if( pUpdateTerrainTempData  && !bOnlyBorder)
	{
		nSrcCount = pUpdateTerrainTempData->m_StripsInfo.idx_array.Count();
		if (!nSrcCount)
			return;	
		pSrcInds = pUpdateTerrainTempData->m_StripsInfo.idx_array.GetElements();
		assert(pSrcInds);
		if(!pSrcInds)
			return;

		pPosPtr = reinterpret_cast<byte *>( pUpdateTerrainTempData->m_lstTmpVertArray.GetElements() );
		uint32 nColorOffset = offsetof(SVF_P2S_N4B_C4B_T1F, color.dcolor );
		nColorStride = sizeof(SVF_P2S_N4B_C4B_T1F);
		pColor = pPosPtr + nColorOffset;

		nNormStride =  sizeof(SVF_P2S_N4B_C4B_T1F);
		uint32 nNormOffset = offsetof(SVF_P2S_N4B_C4B_T1F, normal );
		pNormB = pPosPtr + nNormOffset;
	}
	else
	{
		pRM->LockForThreadAccess();
		// we don't have all data we need, get it from the rendermesh
		nSrcCount = pRM->GetIndicesCount();
		if (!nSrcCount)
			return;
		pSrcInds = pRM->GetIndexPtr(FSL_READ);
		assert(pSrcInds);
		if(!pSrcInds)
			return;
		
		pColor = pRM->GetColorPtr(nColorStride, FSL_READ); 
		pNormB = pRM->GetNormPtr(nNormStride, FSL_READ);

		if (!pColor || !pNormB)
		{
			pRM->UnlockStream(VSF_GENERAL);
			pRM->UnLockForThreadAccess();
			return;
		}

		useMesh = true;
	}


	assert(pColor);

	const float matZAngles = cosf((float)g_PI*GetFloatCVar(e_TerrainDetailMaterialsZAngle)/180);

//	for(int i=0; pColor && i<pRM->GetChunks()->Count(); i++)
	{
	//	CRenderChunk * pChunk = pRM->GetChunks()->Get(i);

    int nVertCount = pRM->GetVerticesCount();

    for (int j=(bOnlyBorder ? nBorderStartIndex : 0); j<nSrcCount; j+=3)
		{
      uint16 arrTriangle[3] = { pSrcInds[j+0], pSrcInds[j+1], pSrcInds[j+2] };

			// ignore invalid triangles
			if(arrTriangle[0] == arrTriangle[1] || arrTriangle[1] == arrTriangle[2] || arrTriangle[2] == arrTriangle[0])
				continue;

      assert(arrTriangle[0]<nVertCount && arrTriangle[1]<nVertCount && arrTriangle[2]<nVertCount);

      if(arrTriangle[0] >= nVertCount || arrTriangle[1] >= nVertCount || arrTriangle[2] >= nVertCount)
        continue;

			UCol & Color0 = *(UCol*)&pColor[arrTriangle[0]*nColorStride];
			UCol & Color1 = *(UCol*)&pColor[arrTriangle[1]*nColorStride];
			UCol & Color2 = *(UCol*)&pColor[arrTriangle[2]*nColorStride];




  #define BYTE_ID(_val) (_val)


			uint32 nVertSurfId0 = Color0.bcolor[BYTE_ID(1)]&127;
			uint32 nVertSurfId1 = Color1.bcolor[BYTE_ID(1)]&127;
			uint32 nVertSurfId2 = Color2.bcolor[BYTE_ID(1)]&127;
/*
			assert(nVertSurfId0<MAX_SURFACE_TYPES_COUNT);
			assert(nVertSurfId1<MAX_SURFACE_TYPES_COUNT);
			assert(nVertSurfId2<MAX_SURFACE_TYPES_COUNT);
*/
      nVertSurfId0 = CLAMP(nVertSurfId0, 0, MAX_SURFACE_TYPES_COUNT-1);
      nVertSurfId1 = CLAMP(nVertSurfId1, 0, MAX_SURFACE_TYPES_COUNT-1);
      nVertSurfId2 = CLAMP(nVertSurfId2, 0, MAX_SURFACE_TYPES_COUNT-1);

			int lstSurfTypes[3]; int idxSurfTypes(0);
			lstSurfTypes[idxSurfTypes++] = nVertSurfId0;
			if(nVertSurfId1 != nVertSurfId0)
				lstSurfTypes[idxSurfTypes++] = nVertSurfId1;
			if(nVertSurfId2 != nVertSurfId0 && nVertSurfId2 != nVertSurfId1)
				lstSurfTypes[idxSurfTypes++] = nVertSurfId2;

			int lstProjIds[3]; int idxProjIds(0);

			byte *pNorm;
			Vec3 vNorm;

			// if there are 3d materials - analyze normals
			if(arrMat3DFlag[nVertSurfId0])
			{
				pNorm = &pNormB[arrTriangle[0]*nNormStride];
				vNorm.Set(2*((float)pNorm[BYTE_ID(0)]/255) - 1, 2*((float)pNorm[BYTE_ID(1)]/255) - 1, 2*((float)pNorm[BYTE_ID(2)]/255) - 1);
				int nProjId0 = GetVecProjectId(vNorm, matZAngles);
				lstProjIds[idxProjIds++] = nProjId0;
			}

			if(arrMat3DFlag[nVertSurfId1])
			{
				pNorm = &pNormB[arrTriangle[1]*nNormStride];
				vNorm.Set(2*((float)pNorm[BYTE_ID(0)]/255) - 1, 2*((float)pNorm[BYTE_ID(1)]/255) - 1, 2*((float)pNorm[BYTE_ID(2)]/255) - 1);
				int nProjId1 = GetVecProjectId(vNorm, matZAngles);
				if(idxProjIds == 0 || lstProjIds[0] != nProjId1)
					lstProjIds[idxProjIds++] = nProjId1;
			}

			if(arrMat3DFlag[nVertSurfId2])
			{
				pNorm = &pNormB[arrTriangle[2]*nNormStride];
				vNorm.Set(2*((float)pNorm[BYTE_ID(0)]/255) - 1, 2*((float)pNorm[BYTE_ID(1)]/255) - 1, 2*((float)pNorm[BYTE_ID(2)]/255) - 1);
				int nProjId2 = GetVecProjectId(vNorm, matZAngles);
				if((idxProjIds < 2 || lstProjIds[1] != nProjId2) &&
					 (idxProjIds < 1 || lstProjIds[0] != nProjId2))
					lstProjIds[idxProjIds++] = nProjId2;
			}

			// if not 3d materials found
			if(!arrMat3DFlag[nVertSurfId0] || !arrMat3DFlag[nVertSurfId1] || !arrMat3DFlag[nVertSurfId2])
				lstProjIds[idxProjIds++] = 3;

			for(int s=0; s<idxSurfTypes; s++)
			{
				for(int p=0; p<idxProjIds; p++)
				{
          assert(lstSurfTypes[s]>=0 && lstSurfTypes[s]<MAX_SURFACE_TYPES_COUNT);
          assert(lstProjIds[p]>=0 && lstProjIds[p]<4);
					PodArray<uint16> & rList = m_arrIndices[lstSurfTypes[s]][lstProjIds[p]];

          if(!bOnlyBorder && j>=nBorderStartIndex && arrpNonBorderIdxNum[lstSurfTypes[s]][lstProjIds[p]]<0)
            arrpNonBorderIdxNum[lstSurfTypes[s]][lstProjIds[p]] = rList.Count();

					rList.AddList(arrTriangle, 3);
				}
			}
		}
	}















	if( useMesh )
	{
		pRM->UnlockStream(VSF_GENERAL);
		pRM->UnLockForThreadAccess();
	}
}

void CTerrainNode::UpdateSurfaceRenderMeshes(IRenderMesh * pSrcRM, SSurfaceType * pSurface, IRenderMesh * & pMatRM, int nProjectionId, 
                                             PodArray<unsigned short> & lstIndices, const char * szComment, bool bUpdateOnlyBorders, int nNonBorderIndicesCount)
{
	FUNCTION_PROFILER_3DENGINE;

//	static PodArray<unsigned short> lstIndices; lstIndices.Clear();
	//GenerateIndicesForSurface(pSrcRM, pSurface->ucThisSurfaceTypeId, lstIndices, pSurface->IsMaterial3D() ? nProjectionId : -1);

  const size_t nIndices = (bUpdateOnlyBorders) ? nNonBorderIndicesCount + lstIndices.Count() : lstIndices.Count();
	if(!pMatRM)
	{
		pMatRM = GetRenderer()->CreateRenderMeshInitialized(
			NULL, 0, eVF_P2S_N4B_C4B_T1F, NULL, 0,
			prtTriangleList, szComment, szComment, eRMT_Static, 1, 0, NULL, NULL, false, false);
	}

	uint8 szProj[] = "XYZ";

	pMatRM->LockForThreadAccess();
	pMatRM->SetVertexContainer(pSrcRM);

  assert(1||nNonBorderIndicesCount>=0);

  if(bUpdateOnlyBorders)
  {
    int nOldIndexCount = pMatRM->GetIndicesCount();

    if (nNonBorderIndicesCount<0 || !nOldIndexCount)
		{
			pMatRM->UnLockForThreadAccess();
      return; // no borders involved
		}

    uint16 *pIndicesOld = pMatRM->GetIndexPtr(FSL_READ);

    int nIndexCountNew = nNonBorderIndicesCount + lstIndices.Count();

    assert(true || nIndexCountNew>=lstIndices.Count());

		//C6255: _alloca indicates failure by raising a stack overflow exception. Consider using _malloca instead
		PREFAST_SUPPRESS_WARNING(6255)
    uint16 * pIndicesNew = (uint16*) alloca(sizeof(uint16) * nIndexCountNew);

    memcpy(pIndicesNew, pIndicesOld, sizeof(uint16)*nNonBorderIndicesCount);

    memcpy(pIndicesNew+nNonBorderIndicesCount, lstIndices.GetElements(), sizeof(uint16)*lstIndices.Count());

    pMatRM->UpdateIndices(pIndicesNew, nIndexCountNew, 0, 0u);
    pMatRM->SetChunk(pSurface->GetMaterialOfProjection(szProj[nProjectionId]), 0, pSrcRM->GetVerticesCount(), 0, nIndexCountNew, 1.0f);
  }
  else
  {
    pMatRM->UpdateIndices(lstIndices.GetElements(), lstIndices.Count(), 0, 0u);
	  pMatRM->SetChunk(pSurface->GetMaterialOfProjection(szProj[nProjectionId]), 0, pSrcRM->GetVerticesCount(), 0, lstIndices.Count(), 1.0f);
  }

	//pMatRM->SetUpdateFrame(pSrcRM->GetUpdateFrame());

	assert(nProjectionId>=0 && nProjectionId<3);
	float * pParams = pSurface->arrRECustomData[nProjectionId];
	SetupTexGenParams(pSurface,pParams,szProj[nProjectionId],true);

	// set surface type
	if(pSurface->IsMaterial3D())
	{
		pParams[ 8] = (nProjectionId == 0);
		pParams[ 9] = (nProjectionId == 1);
		pParams[10] = (nProjectionId == 2);
	}
	else
	{
		pParams[ 8] = 1.f;
		pParams[ 9] = 1.f;
		pParams[10] = 1.f;
	}

	pParams[11] = pSurface->ucThisSurfaceTypeId;

	// set texgen offset
	Vec3 vCamPos = GetCamera().GetPosition();

	pParams[12] = pParams[13] = pParams[14] = pParams[15] = pParams[16] = pParams[17] = 0;

	// for diffuse
	if(IMaterial * pMat = pSurface->GetMaterialOfProjection(szProj[nProjectionId]))
		if(pMat->GetShaderItem().m_pShaderResources)
	{
		if (SEfResTexture * pTex = pMat->GetShaderItem().m_pShaderResources->GetTexture(EFTT_DIFFUSE))
		{
			float fScaleX = pTex->m_bUTile ? pTex->GetTiling(0) : 1.f;
			float fScaleY = pTex->m_bVTile ? pTex->GetTiling(1) : 1.f;

			pParams[12] = int(vCamPos.x*fScaleX)/fScaleX;
			pParams[13] = int(vCamPos.y*fScaleY)/fScaleY;
		}

		if(	SEfResTexture * pTex = pMat->GetShaderItem().m_pShaderResources->GetTexture(EFTT_BUMP))
		{
      float fScaleX = pTex->m_bUTile ? pTex->GetTiling(0) : 1.f;
      float fScaleY = pTex->m_bVTile ? pTex->GetTiling(1) : 1.f;

			pParams[14] = int(vCamPos.x*fScaleX)/fScaleX;
			pParams[15] = int(vCamPos.y*fScaleY)/fScaleY;
		}

		if(	SEfResTexture * pTex = pMat->GetShaderItem().m_pShaderResources->GetTexture(EFTT_DETAIL_OVERLAY))
		{
			float fScaleX = pTex->m_bUTile ? pTex->GetTiling(0) : 1.f;
			float fScaleY = pTex->m_bVTile ? pTex->GetTiling(1) : 1.f;

			pParams[16] = int(vCamPos.x*fScaleX)/fScaleX;
			pParams[17] = int(vCamPos.y*fScaleY)/fScaleY;
		}
	}

		pParams[18] = cosf((float)g_PI*GetFloatCVar(e_TerrainDetailMaterialsZAngle)/180);

	assert(8+8<=ARR_TEX_OFFSETS_SIZE_DET_MAT);

	if (pMatRM->GetChunks().Count() && pMatRM->GetChunks()[0].pRE)
  {
		pMatRM->GetChunks()[0].pRE->m_CustomData = pParams;
    pMatRM->GetChunks()[0].pRE->mfUpdateFlags(FCEF_DIRTY);
  }

	Vec3 vMin, vMax;
	pSrcRM->GetBBox(vMin, vMax);
	pMatRM->SetBBox(vMin, vMax);
	pMatRM->UnLockForThreadAccess();
//  if( pMatRM->GetSysIndicesCount() && pMatRM->GetSysIndicesCount() < pSrcRM->GetSysIndicesCount() / 8 )
  //  pMatRM->UpdateBBoxFromMesh();
}

STerrainNodeLeafData::~STerrainNodeLeafData()
{
  if ( m_pRenderMesh )
	  gEnv->pRenderer->DeleteRenderMesh(m_pRenderMesh);
}

/*void CTerrainNode::OnLightMapDeleted(IDynTexture * pLightMap)
{
	if(m_nTexSet.pLightMap == pLightMap)
		m_nTexSet.pLightMap = NULL;

	if(m_arrChilds[0])
		for(int i=0; i<4; i++)
			m_arrChilds[i]->OnLightMapDeleted(pLightMap);
}*/

void CTerrainUpdateDispatcher::RemoveJob (CTerrainNode* pNode)
{
	int index = m_arrRunningJobs.Find(pNode);
	if (index >= 0)
	{
		m_arrRunningJobs.Delete(index);
		return;
	}

	index = m_queuedJobs.Find(pNode);
	if (index >= 0)
	{
		m_queuedJobs.Delete(index);
		return;
	}
}

#undef USE_SPU

